	TITLE	RAMDRIV5.MAC
;
;***********************************************************************
;
;	RAMDRIV - A program that permits extended memory to function as
;	a fast disk drive.  This program does not require modification
;	of the existing BIOS.
;
;	Copyright 1982, by
;	Herbert B. Shore
;	Department of Physics
;	San Diego State University
;	San Diego, CA 92182
;	(619) 265-6159
;
;	Released to the public domain for non-commercial use.
;
;	RAMDRIV has been extensively modified by Paul J. Gans.  Changes
;	are copyright (c) by Paul J. Gans, Department of Chemistry, New
;	York University, New York, NY  10003 (212) 598-2515
;
;	New or modified features include:
;
;	For RAMDRIV5:
;
;	1.  Code has been converted to Z80 code throughout.
;	2.  The target assembler is now Microsoft's M80.
;	3.  The code is now self-loading.  The original RAMLD program is
;		no longer required.
;	4.  It is no longer required to have contiguous memory banks of
;		the same size.  Available memory is controlled by the
;		BLEN table described below.
;	5.  Track size is now a variable multiple of 1K and can be
;		changed through an equate.
;	6.  The original ALV parameter has been dropped in favor of
;		TRAKLEN, the number of kilobytes per track.
;	7.  The DRAM parameter has been dropped.  Extended (bank)
;		addresses are now contained in the BLEN table.
;	8.  The program now uses both the directory buffer and the host
;		buffer in the local BIOS instead of its own buffer.
;		This requires that the user know the addresses of both
;		of these buffers in his or her BIOS.  To accomodate
;		those who choose not to use the BIOS buffers, two DS
;		definitions are required a few lines after the label
;		WBOOT1.  The proper spot is commented in the code
;		below.
;
;
;	INSTALLATION of RAMDRIV5:
;
;	RAMDRIV assumes that the user's system has "extra" memory
;	available beyond the normal 64K maximum directly addressable
;	in Z80 systems.  This memory may be either "bank select" or
;	S-100 extended addressing.
;
;	RAMDRIV is installed by setting a number of equates and by
;	properly configuring the BLEN table located near the end of
;	the code.  After the proper values have been set by equates and
;	the BLEN table has been set up, the code should be assembled
;	using M80.  The commands for so doing are:
;
;	>M80 =RAMDRIV5/L	<== /L optionally produces a .PRN file
;	>L80 /P:100,RAMDRIV5,RAMDRIV5/N/E
;
;	The equates for DIRBUF and RAMBUF must be set to reflect the
;	location of these buffers in the user's BIOS.  If this is not
;	feasible, see the comments at the label WBOOT1.
;
;	Equates that must be set are defined and explained below.
;
;	In normal use the user must also configure the BLEN table for
;	his or her system and recompile.  There is one BLEN table entry
;	for each bank of memory to be used by RAMDRIV.  It is assumed
;	that each of these banks starts at address 0000 and proceeds
;	upward to some value without any holes.  It is also assumed that
;	at least the upper 1K of system memory is global, thus no bank
;	can exceed 63K.
;
;	A BLEN table entry consists of two bytes for each bank of memory
;	to be used by RAMDRIV.  The first byte is the number of kilo-
;	bytes available for RAMDRIV in that bank.  This value is placed
;	into the BLEN table as the value divided by TRAKLEN.  The actual
;	number of kilobytes of that bank used by RAMDRIV is the integral
;	part of the division.  For instance a 30K bank of memory would
;	be indicated to the system as 30/TRAKLEN.
;
;	The second byte in the BLEN table entry	is the extended (or 
;	bank) address byte for that bank of memory.  There is one such 
;	entry pair for each bank.  The table is terminated by a single 
;	zero byte.
;
;	Thus a system with 20K at a bank addressed as 0DEH and 8K at a
;	bank addressed as 70H could have a BLEN table as:
;
;		BLEN:	DB	20/TRAKLN,0DEH
;			DB	08/TRAKLN,70H
;			DB	0
;
;	while the same system in which the user wanted to reserve 4K on
;	the board at 0DEH for some other purpose could have a BLEN table
;	as:
;
;		BLEN:	DB	16/TRAKLN,0DEH
;			DB	08/TRAKLN,70H
;			DB	0
;
;	
;************************************************************************
;
;  The code and tables in this file will reside in high memory, above the
;  existing BIOS.  The system is self-loading.  Typing RAMDRIV3 from the
;  console is sufficient to initiate RAMDRIV3.
;
	.Z80
;
FALSE	EQU	0
TRUE	EQU	NOT FALSE
;
;  The following equates are system dependent.
;
;  DIRBUF:  This is the address of the CP/M directory buffer in the
;    user's BIOS.  CP/M 2.x systems are required to have such a buffer
;    available.  The address will vary in different implementations.
;
;  RAMBUF:  This is the address of the CP/M host buffer in the user's
;    BIOS.  In a normal CP/M 2.x system this buffer will be at least 1K
;    long.  The address will vary in different implementations.
;
;  PANEL:  If you have an (IMSAI-type) front panel, set PANEL=TRUE.  The
;    lights will display the extended address, simulating the LED on the
;    door of the disk drive.  Flipping switch D0 up will "write protect"
;    the RAM drive.
;
;  RAMDRIV:  Set to a memory location above BIOS.  Less than 1K of
;    GLOBAL memory is required at this location; i.e. the same block
;    of memory must be accessible independent of the setting of the ex-
;    tended address bus or any bank byte.
;
;  PRAM:  The extended address of regular program RAM.  Usually this is
;    00H, but need not be.
;
;  XPORT:  The output port that sets the extended address bus or bank
;    byte.
;
;  DNAME:  The alphabetic designation of the RAM drive.
;
;  LIGHTS:  The output port for front panel lights.  This is signif-
;    icant only if PANEL is TRUE.
;
;  SWITCH:  The input port for front panel switches.  This is signif-
;    icant only if PANEL is TRUE.
;
;  TRAKLN:  The number of kilobytes per track.  This must be a minimum
;    of 1K and a maximum no greater than the smallest bank of memory to
;    be made available to RAMDRIV.  A good value to use is the greatest
;    common divisor of each of the memory banks to be used by RAMDRIV.
;
;  DIRECT:  The number of directory entries permitted on the RAM drive.
;    The number specified must be a multiple of 32.  I used 32 to leave
;    the maximum space for program files.
;
	.Z80
;
DIRBUF	EQU	0F77FH		; CP/M BIOS directory buffer address
RAMBUF	EQU	0F7FFH		; CP/M BIOS host buffer
PANEL	EQU	FALSE		;IMSAI FRONT PANEL
RAMDRIV	EQU	0FC00H		;START OF PROGRAM.
PRAM	EQU	00H		;EXTENDED ADDR. OF MAIN RAM
XPORT	EQU	0FDH		;PORT TO SET EXT. ADDR.
DNAME	EQU	'C'		;DRIVE NAME OF RAMDRIV.
	IF	PANEL
LIGHTS	EQU	0FFH		;FRONT PANEL LIGHTS.
SWITCH	EQU	0FFH		;FRONT PANEL SWITCHES.
	ENDIF	
TRAKLN	EQU	 4		; kilobytes per track
DIRECT	EQU	32		;NO. OF DIRECTORY ENTRIES.
;
;  These equates will normally not need to be changed.
;
BDOS	EQU	5
DELCHR	EQU	0E5H		;DELETED DIRECTORY ENTRY.
DNUMB	EQU	DNAME - 'A'	;RAMDRIV DRIVE NUMBER
CR	EQU	0DH
LF	EQU	0AH
;
;
;  The code below replaces the original loader code, since that
;  mechanism will not work on a program compiled under M80.  The
;  M80 .PHASE commands have been used rather than the original
;  ORG's.
;
	LD	DE,SIGNON	; print signon message from loader
	LD	C,9
	CALL	BDOS
;
	LD	HL,(0001)	; check to see if BIOS modified
	INC	HL		; point to jump vector
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	EX	DE,HL
	LD	DE,RAMDRIV
	AND	A		; clear carry
	SBC	HL,DE		; WBOOT minus RAMDRIV
	JP	C,CONTIN	; if carry we are OK
	LD	DE,NOWAY
	LD	C,9
	CALL	BDOS
	JP	0
;
SIGNON:	DB	'RAMDRIV5'
	DB	CR,LF,'$'
NOWAY:	DB	'Cannot load RAMDRIV',CR,LF
	DB	'Cold boot system and try again'
	DB	CR,LF,'$'
;
CONTIN:	LD	HL,SETUP
	LD	DE,RAMDRIV
	LD	BC,1023
	LDIR
	JP	RAMDRIV
;
	.PHASE	RAMDRIV
;
;  ORIGINAL JUMP TABLE FROM BIOS
;
WBOOT1:	DS	48
WCK:				;END OF BIOS TABLE.
;
CSV:	DS	DIRECT/4	; this should probably be zero since no
;				    checking is done
ALV:	DS	72		; this is allocation vector space which
;				    must have 1 bit available per block.
;				    this value corresponds to 576 1K
;				    blocks.
;
;  If the user does not wish to (or cannot) use the directory and host
;  buffers in the system BIOS, the buffers can alternatively be defined
;  here.  In this case the following lines should be uncommented and the
;  corresponding equates above commented.
;
;DIRBUF: DS	128		; directory buffer
;RAMBUF: DS	128		; bank communication buffer
;
ENDTBL:				;END OF DATA TABLES.
;
	.DEPHASE
;
;  The following code is executed once to set up the RAMDRIV system.  In
;  order to conserve space it will be overwritten by data buffers during
;  actual access to RAMDRIV.
;
;
SETUP:	
;
	.PHASE	RAMDRIV
;
;  Determine the amount of available RAMDRIV memory
;
	XOR	A
	LD	C,A
	LD	HL,BLEN		; RAMDRIV memory table
	LD	DE,0002		; table entry increment
BCHEK0:	LD	A,(HL)		; get bank kilobytesa
	OR	A		; zero means end of table
	JR	Z,BCHEK1
	ADD	A,C		; accumulate count
	LD	C,A		; and save it
	ADD	HL,DE		; point to next BLEN entry
	JR	BCHEK0
;
BCHEK1:	CP	C		; is there any ram available at all?
	JR	NZ,RAMOK	; jump if there is
	LD	DE,NORAM	; no ram, tell the user
	LD	C,9
	CALL	BDOS
	RET			; avoid warm boots!
;
NORAM:	DB	'Cannot activate RAMDRIV.  No available RAM.'
	DB	CR,LF,'$'
;
RAMOK:	XOR	A		; clear counter
	LD	B,TRAKLN	; the count must be multiplied
;				    by TRAKLN to yeild kilobytes
RAMOK1:	ADD	A,C		; multiply by repeated adding
	DJNZ	RAMOK1
;
	DEC	A		; RAMDRIV kilobytes minus 1
	LD	(DSM),A		; maximum block number to DPB
;
;  MODIFY BIOS JUMP TABLES
;
	IF	$ LT WCK	;MAKE SURE THAT WBOOT1 TABLE
	JP	BJUMP		;WILL NOT OVERWRITE
	ENDIF			;BJUMP ROUTINE.
	IF	$ LT WCK
	DEFS	WCK - $
	ENDIF	
;
BJUMP:	LD	HL,(1)		;GET ADDRESS OF WARM BOOT
	LD	DE,WBOOT1	;TRANSFER TABLE FROM BIOS UP HERE
	LD	BC,48		;48 BYTES
	LDIR	
	LD	HL,WBOOT2	;TRANSFER OUR TABLE TO BIOS
	LD	DE,(0001)
	LD	BC,48
	LDIR	
;
;  PRINT MESSAGE AND ASK QUESTION
;
	LD	DE,QUEST
	LD	C,9		;PRINT STRING
	CALL	BDOS
	LD	C,1		;GET CHARACTER
	CALL	BDOS
	RES	5,A		;CONVERT TO UPPER CASE.
	CP	'Y'
	JP	NZ,QUIT
;
;  CLEAR DIRECTORY OF RAMDRIV
;
	LD	BC,0000		; directory on track 0 sector 0
	CALL	MAP
	OUT	(XPORT),A
	IF	PANEL
	CPL	
	OUT	(LIGHTS),A	;TO IMSAI FRONT PANEL
	ENDIF	
	EX	DE,HL		; HL = base location of directory
	LD	DE,32		;EVERY 32 BYTES
	LD	B,DIRECT	;NO. OF ENTRIES.
	LD	A,DELCHR
CLEAR:	LD	(HL),A
	ADD	HL,DE
	DJNZ	CLEAR
	LD	A,PRAM		;RESET EXT. ADDR.
	OUT	(XPORT),A
	IF	PANEL
	CPL	
	OUT	(LIGHTS),A
	ENDIF	
;
QUIT:	LD	DE,CRLF
	LD	C,9
	CALL	BDOS
	JP	0		;BACK TO CP/M.
;
QUEST:	DB	CR,LF
	DB	'RAMDRIV active on drive ',DNAME
	DB	CR,LF
	DB	'Should directory of drive ',DNAME
	DB	' be cleared (Y/N)? $'
CRLF:	DB	CR,LF,'$'
;
;  NEW TABLE COPIED INTO BIOS
;
WBOOT2:	JP	WBOOT
	JP	WBOOT1+3
	JP	WBOOT1+6
	JP	WBOOT1+9
	JP	WBOOT1+12
	JP	WBOOT1+15
	JP	WBOOT1+18
	JP	HOME		;21
	JP	SELDSK		;24
	JP	SETTRK		;27
	JP	SETSEC		;30
	JP	SETDMA		;33
	JP	READ		;36
	JP	WRITE		;39
	JP	WBOOT1+42
	JP	SECTRN		;45
;
;  IMPLEMENT BIOS FUNCTIONS
;
	IF	$ LT ENDTBL	;MAKE SURE THAT BIOS
	DEFS	ENDTBL - $	; FUNCTIONS BEGIN
	ENDIF			; AFTER TABLES.
;
WBOOT:	LD	HL,80H		;SET INITIAL DMA ADDR.
	LD	(DMAADR),HL
	JP	WBOOT1		;BACK TO BIOS
;
;
HOME:	LD	A,(DISK)	;CHECK DISK NUMBER.
	CP	DNUMB		;IS IT THE RAMDRIV?
	JP	NZ,WBOOT1+21	;IF NOT, LET BIOS HANDLE IT.
	LD	A,0		;SET "TRACK" TO 0
	LD	(XTRAK),A
	RET	
;
;
SELDSK:	LD	A,C		;CHECK REQUESTED DISK,
	LD	(DISK),A
	CP	DNUMB
	JP	NZ,WBOOT1+24
	LD	HL,DPH		;RETURN DISK PAR. HEADER.
	RET	
;
;  DISK PARAMETER HEADER FOR RAMDRIV
;
DPH:	
	DW	0		;NO SECTOR TRANSLATION
	DW	0,0,0		;SCRATCH
	DW	DIRBUF		;LOC. OF SCRATCHPAD AREA.
	DW	DPB		;DISK PARAMETER BLOCK
	DW	CSV		;DIR. CHECK AREA
	DW	ALV		;ALLOC. VECTOR.
;
;  DISK PARAMETER BLOCK
;
DPB:	
	DW	(1024*TRAKLN)/128	;SECTORS PER TRACK
	DB	3		;BSH
	DB	7		;BLM
	DB	0		;EXM
DSM:	DW	62		;MAX BLOCK NO.
	DW	DIRECT-1	;HIGHEST DIRECT. NO.
;
AL0	EQU	LOW (0FFH SHL (8-DIRECT/32))
AL1	EQU	0
;
	DB	AL0,AL1		;DIRECTORY BLOCKS.
;
CKS	EQU	0		;CHECK DIRECT. ENTRIES.
;
	DW	CKS
	DW	0		;NO SKIPPED TRACKS.
;
;
SETTRK:	LD	A,(DISK)
	CP	DNUMB
	JP	NZ,WBOOT1+27
	LD	A,C		;TRACK NO.
	LD	(XTRAK),A
	RET	
;
;
SETSEC:	LD	A,(DISK)
	CP	DNUMB
	JP	NZ,WBOOT1+30
	LD	(SECTOR),BC
	RET	
;
;
SETDMA:	LD	(DMAADR),BC	;SAVE DMA ADDR. BOTH HERE
	JP	WBOOT1+33	;AND IN BIOS.
;
;
READ:	LD	A,(DISK)
	CP	DNUMB
	JP	NZ,WBOOT1+36
	LD	BC,(SECTOR)	; but we asssume sector fits in one byte
	LD	A,(XTRAK)	;SET EXTENDED ADDRESS.
	LD	B,A
	CALL	MAP		; to compute bank and offset
	OUT	(XPORT),A
	IF	PANEL
	CPL	
	OUT	(LIGHTS),A
	ENDIF	
	EX	DE,HL
	LD	DE,RAMBUF	;TEMPORARY BUFFER.
	LD	BC,128		;SECTOR=128 BYTES.
	LDIR	
	LD	A,PRAM		;RESET ADDR.
	OUT	(XPORT),A
	IF	PANEL
	CPL	
	OUT	(LIGHTS),A
	ENDIF	
	LD	HL,RAMBUF	;MOVE FROM BUFFER
	LD	DE,(DMAADR)	; back to main ram
	LD	BC,128
	LDIR	
	XOR	A		; show no errors
	RET	
;
;
WRITE:	LD	A,(DISK)
	CP	DNUMB
	JP	NZ,WBOOT1+39
	IF	PANEL
	IN	A,(SWITCH)	;CHECK D0 OF FRONT PANEL
	AND	1
	RET	NZ		;RETURN IF WRITE PROTECT.
	ENDIF	
	LD	HL,(DMAADR)	;DMA --> BUFFER
	LD	DE,RAMBUF
	LD	BC,128
	LDIR	
;
	LD	BC,(SECTOR)	; but we assume sector < 256
	LD	A,(XTRAK)
	LD	B,A
	CALL	MAP
	OUT	(XPORT),A
	IF	PANEL
	CPL	
	OUT	(LIGHTS),A
	ENDIF	
	LD	HL,RAMBUF	;BUFFER --> RAMDRIV.
	LD	BC,128
	LDIR
;
	LD	A,PRAM
	OUT	(XPORT),A
	IF	PANEL
	CPL	
	OUT	(LIGHTS),A
	ENDIF	
	XOR	A		; show no error
	RET	
;
;
SECTRN:	LD	A,(DISK)
	CP	DNUMB
	JP	NZ,WBOOT1+45
	LD	H,B
	LD	L,C
	RET	
;
;
;  Subroutine to calculate RAM address and bank
;
;	called with  B = track number (starts from 0)
;		     C = sector number (starts from 0)
;
;	returns with A = extended address byte
;		    DE = offset into bank for start of sector
;
MAP:	XOR	A		; clear A
	LD	A,B		; track number to A
	LD	DE,2		; increment for BLEN table
	LD	HL,BLEN
;
MAP1:	SUB	(HL)		; subtract bank length in K's
	JR	C,MAP2		; jump if track in this bank
	ADD	HL,DE		; point to next BLEN entry
	JR	MAP1		; repeat till done
;
MAP2:	ADD	A,(HL)		; A = relative track number in bank
	RLCA			; multiply by 4K
	RLCA
	RLCA
	RLCA
	LD	E,D		; clear E
	LD	D,C		; multiply sector number by 128
	RR	D
	RR	E
	ADD	A,D		; add to track starting address
	LD	D,A		; DE = sector start in bank
	INC	HL
	LD	A,(HL)		; A = extended (bank) address
	RET
;
BLEN:	DB	32/TRAKLN,0FFH	; first bank has 32K at FF
	DB	32/TRAKLN,0FEH	; second bank has 32K at FE
	DB	04/TRAKLN,001H	; third bank has 4K at 01
	DB	0		; end of table
;
;  DATA STORAGE AREA
;
DISK:	DB	0
XTRAK:	DB	0
DMAADR:	DW	80H
SECTOR:	DW	0
;
	.DEPHASE
;
	END	
